﻿
/*
EC-IDE class, from dom.Base template,
	SyntaxViewer
	org.supertree.editor.SyntaxViewer

Author: Feng Weiguo ( 冯卫国 )
Mail: forxm@21cn.com
Web: www.supertree.org

All Rights Reserved

LICENSE: LGPL

Created: 2008-12-03 08:40:36

Updated: 2008-12-10

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License (LGPL) as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.
*/

//#EC_PARAM {linenum:'b',wrap:'b',size:'n',linebreak:'b',fontsize:[16,14,12],nobold:'b',readonly:'b',text:'s',rule:'s'}
//linebreak: should line break icon at line end
//nobold: disable bold font
//readonly: readonly flag for SyntaxEditer; SyntaxViewer doesn't used this flag and is always readonly.
//text: text data
//rule: rule name

$ec.$preload(
	"org.supertree.editor.SyntaxViewer",
	function()
	{
		this.prototype=$ec.$derive(
			"org.supertree.dom.Base",
			{
//////////////////////////////
// EC-CLASS BODY
//////////////////////////////
			
			eid: "",    //binding element id
			
			//////////////////
			//sub element variable
			//////////////////
			
			eidText: "",	//the <OL>
			
			eidTmp: "", //ei for text length caculation
			
			//////////////////
			//view variable
			//////////////////
			
			nMaxLine: 0,	//max line to reach, for margin-left calculation.
			bLineNumber: 1,	//line number flag
			
			nReadonly: 0,	//readonly flag: 0:not read only, 0x1:readonly, 0x2: internal usage, wrap readonly
			bWrap: 0,		//line wrap flag
			
			sFont: "",	//font size string, "":default 16px, "14": 14px, "12":12px
			
			bLineBreak: 1,		//show line break icon, it's better to set this flag when instance created.
			
			nBoldMode: 0,	//bold font mode, 0/default:enable bold font, 1:disable bold font, 2:disable bold font by rule
			bNoBold: 0,		//no bold font flag
			
			nLineHeight:0,	//font unit height
			nCharWidth:0,	//font unit width
			
			//////////////////
			//rule management variable
			//////////////////
			
			//static
			aRule: [],	//rule array, [ [rule1,name1], [rule2,name2], ... ]
			nRuleIdx: 0,	//current rule index
			
			oRuleText: {	//default text rule
				sName: "Text",
				rule: [/($)/g ,["","",0]],
				sEndStack: "|0|"
				},
			
			oRule: null, //current rule object
			ruleTop: null,	//current rule
			sEndStack: "",	//current rule end stack
			
			//////////////////
			//syntax format variable
			//////////////////
			
			aStkCache1: null,		//cache for current formatting
			aStkCache2: null,		//cache for timeout formatting
			
			aUpdateList: null,		//timeout updating list
			nUpdateLine: 0,			//for timeout updating report, estimate
			
			sStackSplit: ",",		//stack split char
			
			sBlockSpan1: (window.ActiveXObject)?"<SPAN class=":"<span class=\"",	//optimize for ie/firefox, matched the innerHTML return from the browser.
			sBlockSpan2: (window.ActiveXObject)?">":"\">",
			sBlockSpan3: (window.ActiveXObject)?"</SPAN>":"</span>",
			
			spa: [ ""," ", "  ","   ","    " ],	//space array cache
			
			//////////////////
			//for SyntaxEditor
			//////////////////
			
			//bEditor: 0,
			
			//UpdateCaret: $ec._void,		//overridable function
			//ShowBlock: $ec._void,		//overridable function
			
			//////////////////
			//constructor
			//////////////////
			
			SyntaxViewer: function( e, p )	//ec-class constructor
			{
				this.eid= $ec.$eid(e);      //assign and get unique element id
				
				//this innerHTML
				e.innerHTML=
					"<div style='position:relative;left:0px;top:0px;z-index:5;'>"+
						"<ol name='text' style=\"padding-left:0px;margin-left:3em;padding-top:0px;white-space:nowrap;margin-top:0px;margin-bottom:0px;font-family:'宋体','Courier New';font-size:16px;letter-spacing:1px;\"><li></li></ol>"+
					"</div>"
					+(this.bEditor?
						("<div name='block1' style='font-size:1px;width:100px;height:100px;position:absolute;display:none;z-index:1;'></div>"+
						"<div name='block2' style='font-size:1px;width:100px;height:100px;position:absolute;display:none;z-index:1;'></div>"+
						"<div name='block3' style='font-size:1px;width:100px;height:100px;position:absolute;display:none;z-index:1;'></div>"+
						"<textarea name='caret' type='text' style=\""+
							((this.sAgent=="safari"||this.sAgent=="chrome")?"outline:0 none;resize:none;":"")+	//for safari and chrome
						"border:none;padding:0px;margin:0px;width:3px;position:absolute;left:0px;top:0px;background-color:transparent;height:18px;overflow-x:hidden;overflow-y:hidden;z-index:10;font-family:'宋体','Courier New';font-size:16px;letter-spacing:1px;\" wrap=off></textarea>"
						):""
					);
				
				//sub element
				var ei,eiText;
				eiText= $ec(e,"text");
				this.eidText= $ec.$eid( eiText );
				
				this.eidTmp=$ec.$eid("");	//pre-set tmp value
				
				//other
				//alert(this.sAgent);
				if(this.sAgent=="ie" )
				{
					//open the "hasLayout" property in ie6 to speed up
					eiText.parentNode.contentEditable=true;
					eiText.parentNode.contentEditable=false;
				}
				/*
				else if( this.sAgent=="firefox" )
				{
					//alert("h");
					
					//speed up for firefox
					//document.designMode="on";
					//document.designMode="off";
				}
				*/
				
				//parameters
				if(p)
				{
					if(typeof p.linenum!="undefined" ) this.SetLineNumber( p.linenum );
					if(typeof p.wrap!="undefined" ) this.SetWrap( p.wrap );
					if(typeof p.linebreak!="undefined" ) this.SetLineBreak( p.linebreak );
					if(typeof p.fontsize!="undefined" ) this.SetFontSize( p.fontsize );
					if(typeof p.nobold!="undefined" ) this.SetNoBold( p.nobold );
					if(typeof p.readonly!="undefined" ) this.SetReadonly( p.readonly );
					if(typeof p.rule!="undefined" ) this.SetRule(p.rule);
					if(typeof p.text!="undefined" ) this.SetText(p.text);
				}
				
				//debug
				//this.SetText("var a,b;\n\tvar c,d;\n\nfjasdjjsad  fasjdfaslkd fasdjkf  fjasd fasjdhfkasjdf haskdjf f asdf\n\nvar c,m\nfjasdjjsad  fasjdfaslkd fasdjkf  fjasd fasjdhfkasjdf haskdjf f asdf\nvar c,d中\nsad sadf asdf fas\nsad sadf asdf fas\nsad sadf asdf fas\nsad sadf asdf fas\nsad sadf asdf fas\nsad sadf asdf fas\nsad sadf asdf fas\nsad sadf asdf fas\nsad sadf asdf fas\nsad sadf asdf fas\nsad sadf asdf fas\nsad sadf asdf fas\nsad sadf asdf fas");
				
				//update view
				if( this.nMaxLine==0 ) this.updateLineNumber(1);
			},
			
			//////////////////
			//view function group
			//////////////////
			
			SetLineNumber: function( bNumber )
			{
				if( !bNumber!=!this.bLineNumber )
				{
					this.bLineNumber= bNumber?1:0;
					this.updateLineNumber(1);
				}
			},
			
			ToggleLineNumber: function()
			{
				this.SetLineNumber(!this.bLineNumber);
			},
			
			updateLineNumber: function(bToggle)
			{
				//return;
				
				var m,bChange=0;
				var ei= $ec(this.eidText);
				
				if(bToggle)
				{
					ei.style.listStyleType=this.bLineNumber?"decimal":"none";
					bChange=1;
				}
				
				if( this.bLineNumber )
				{
					m=ei.childNodes.length;
					if(m<10)m=99;
					
					if( m>this.nMaxLine )
					{
						m= m.toString().replace(/\d/g, "9" );
						this.nMaxLine=parseInt(m);
						m= (window.ActiveXObject?3:3) + (m.length-2)*0.5;
						//if(m<0.5) m=0.5;
						ei.style.marginLeft=m+"em";
						bChange=1;
						
						this.updateBackground();
					}
				}
				else
				{
					if( this.nMaxLine || bToggle)
					{
						this.nMaxLine=0;
						ei.style.marginLeft=window.ActiveXObject?"0.5em":"1em";		//for firefox, if less than 1 em, scroll state will be abnormal
						//alert("h");
						bChange=1;
					}
				}
				
				//set background
				if(bToggle) this.updateBackground();
				
				if( bChange && this.pos )	//for SyntaxEditor
				{
					this.UpdateCaret(this.bFocus);
					this.ShowBlock();
				}
			},
			
			updateBackground: function()
			{
				//return;
				
				var ei= $ec(this.eidText);
				var ep=ei.parentNode;
				if( this.bLineNumber )
				{
					/*
					*/
					ep.style.backgroundImage="url("+$ec.$file[(this.bFocus && !this.nReadonly)?"SyntaxEditorBackActive_gif":"SyntaxEditorBack_gif"]+")";
					//alert(ep.style.backgroundImage);
					
					ep.style.backgroundRepeat="repeat-y";
					//$ec.TRACE(ei.childNodes[0].offsetLeft + " " + ei.parentNode.offsetLeft );
					ep.style.backgroundPosition="-"+(200-(ei.childNodes[0].offsetLeft)+5)+"px 0px";	//200: width of bk-pic
				}
				else
				{
					ep.style.background="none";
				}
			},
			
			SetWrap: function( bWrap )
			{
				if( !bWrap!=!this.bWrap )
				{
					this.bWrap= bWrap?1:0;
					var ei= $ec(this.eidText);
					if( !bWrap )
					{
						ei.style.wordWrap="";
						ei.style.whiteSpace="nowrap";
						this.setReadonly( this.nReadonly & ~0x2 );
					}
					else
					{
						ei.style.wordWrap="break-word";
						ei.style.whiteSpace="";
						this.setReadonly( this.nReadonly | 0x2 );
					}
					if( this.pos ) this.resetBlock();	//for SyntaxEditor
				}
			},
			
			ToggleWrap: function()
			{
				this.SetWrap(!this.bWrap);
			},
			
			setReadonly: function( nReadonly )
			{
				this.nReadonly=nReadonly & 0x3;		//0x3: mask for all
				
				if( this.eidCaret )		//for SyntaxEditor
				{
					var eiCaret= $ec(this.eidCaret);
					if( this.nReadonly )
					{
						eiCaret.readOnly=true;
						//eiCaret.style.visibility="hidden";
						
						this.OnBlur();
						this.updateBackground();
					}
					else
					{
						eiCaret.readOnly=false;
						//eiCaret.style.visibility="";
						
						if( this.bFoucs )
						{
							this.OnBlur();
							this.OnFocus();
						}
					}
				}
			},
			
			SetReadonly: function( bReadonly )
			{
				this.setReadonly( bReadonly?(this.nReadonly|0x1):((this.nReadonly&~0x1)));
			},
			
			SetFontSize: function(nSize)
			{
				if( nSize==14) this.sFont="14";
				else if( nSize==12) this.sFont="12";
				else this.sFont="";		//16px
				
				nSize= parseInt(this.sFont?this.sFont:"16");
				
				$ec(this.eidText).style.fontSize=nSize+"px";
				var ei= $ec(this.eidCaret);
				if(ei)
				{
					ei.style.fontSize=nSize+"px";
					ei.style.height=(nSize+2)+"px";
				}
				
				this.updateBackground();
				
				this.updateFontUnit();
			},
			
			updateFontUnit: function()
			{
				var eTmp=this.getTmpLine();
				this.nLineHeight= eTmp.childNodes[1].offsetHeight;
				this.nCharWidth= eTmp.childNodes[1].offsetWidth;
			},
			
			SetLineBreak: function(bShow)
			{
				this.bLineBreak= bShow?1:0;
				this.RefreshFormat();
			},
			
			ToggleLineBreak: function()
			{
				this.SetLineBreak(!this.bLineBreak);
			},
			
			SetNoBold: function( bNoBold )
			{
				this.bNoBold= bNoBold?1:0;
				
				if( this.oRule && this.oRule.bNoBold )
				{
					this.nBoldMode=2;
				}
				else
				{
					this.nBoldMode=  this.bNoBold?1:0;
				}
				
				$ec(this.eidText).style.letterSpacing=this.nBoldMode?"0px":"1px";
				
				if(this.eidCaret) $ec(this.eidCaret).style.letterSpacing=this.nBoldMode?"0px":"1px";	//for SyntaxEditor
				
				this.updateFontUnit();
				this.RefreshFormat();
			},
			
			ToggleNoBold: function()
			{
				this.SetNoBold(!this.bNoBold);
			},
			
			//////////////////
			//line function group
			//////////////////
			
			getTmpLine: function()
			{
				var eTmp=$ec(this.eidTmp);
				if(!eTmp )
				{
					eTmp= $ec(this.eidText).appendChild( document.createElement("LI") );
					eTmp.id= this.eidTmp;
					
					eTmp.style.visibility=this.bDebug?"":"hidden";
					
					//eTmp.style.color="transparent";
					
					eTmp.appendChild( document.createElement("SPAN") );
					eTmp.appendChild( document.createElement("SPAN") ).innerHTML="#";
					
					this.updateFontUnit();
					
					eTmp.parentNode.parentNode.contentEditable=true;
					eTmp.parentNode.parentNode.contentEditable=false;
				}
				return eTmp;
			},
			
			GetLineNumber: function()
			{
				var ei=$ec(this.eidText);
				return ( ei.lastChild.id==this.eidTmp )?(ei.childNodes.length-1):ei.childNodes.length;
			},
			
			//////////////////
			//rule manage function group
			//////////////////
			
			//static
			//AddRule: function( oRule [, sNewName] )
			//rule manage tool
			AddRule: function( oRule, sNewName )
			{
				sNewName= sNewName?sNewName:oRule.sName;
				var i,imax= this.aRule.length;
				for(i=0;i<imax;i++)
				{
					if( this.aRule[i][0]==oRule ) return;	//duplicate
				}
				
				this.aRule[this.aRule.length]=[oRule,sNewName];
			},
			
			//SetRule: function( oRule )
			//SetRule: function( sName )
			//SetRule: function( index )
			SetRule: function( oRule )
			{
				var i=-1,imax= this.aRule.length;
				
				if(typeof oRule=="string")
				{
					for(i=0;i<imax;i++)
					{
						if( this.aRule[i][1]==oRule ) break;
					}
				}
				else if( typeof oRule=="number" )
				{
					i= oRule;
				}
				else
				{
					for(i=0;i<imax;i++)
					{
						if( this.aRule[i][0]===oRule ) break;
					}
				}
				
				if( i<0 || i>=imax ) return;
				
				this.nRuleIdx= i;
				
				this.oRule= this.aRule[i][0];
				this.ruleTop=this.oRule.rule;
				this.sEndStack=this.oRule.sEndStack;
				
				if(!this.eid) return;	//SetRule may call from ec-entry
				
				this.initRule();
			},
			
			//////////////////
			//syntax format group
			//////////////////
			
			//stkCache: stack cache, [str,rule,ruleIndex]
			getHeadStack: function( stkCache, eLine, bThis )
			{
				var str="0";
				
				if( eLine )
				{
					if( bThis ) str= eLine.getAttribute("Stack");
					else if( eLine.previousSibling ) str= eLine.previousSibling.getAttribute("Stack");
				}
				
				if(!str) str="0";
				
				//create stack from string
				if( str!=stkCache[0])
				{
					//alert("update cache");
					var a1= [this.ruleTop];
					var a2= str.split(this.sStackSplit);
					var i,imax,idx,a=a1[0];
					imax=a2.length;
					for(i=1;i<imax;i++)
					{
						if( a2[i].charAt(0)=='s' ) continue;
						
						a=a[a2[i]][4];
						a1[a1.length]=a;
					}
					
					stkCache[0]=str;
					stkCache[1]=a1;
					stkCache[2]=a2;
				}
			},
			
			//start parse from eLine.nextSibling
			updateGroup: function( rule, ruleIndex, eLine, sOldStk )
			{
				var i,ei;
				for(i=0;i<20;i++)	//each timeout 20 lines
				//for(i=0;i<5;i++)
				//for(i=0;i<2;i++)
				{
					if( sOldStk==eLine.getAttribute("Stack") ) return null;
					if( !(ei=eLine.nextSibling) ) return null;		//last line
					if( ei.id==this.eidTmp ) return null;		//last line
					
					sOldStk=(eLine=ei).getAttribute("Stack");
					this._parse( eLine, rule, ruleIndex );
				}
				if( sOldStk==eLine.getAttribute("Stack") ) return null;
				
				if( !(ei=eLine.nextSibling) ) return null;		//last line
				if( ei.id==this.eidTmp ) return null;		//last line
				
				return ei;
			},
			
			//for hta, has no status
			Status: function(s)
			{
				window.status=s;
			},
			
			//FormatLine: function(idxLine)
			//include idxLine
			FormatLine: function(idxLine, bSingle)
			{
				var eLine= document.getElementById( this.eidText).childNodes[idxLine];
				
				if(!eLine ) return;
				
				if( !this.aStkCache1 ) this.aStkCache1=["",0,0];
				var stk=this.aStkCache1;
				this.getHeadStack( stk, eLine );
				var rule=[].concat(stk[1]);
				var ruleIndex=[].concat(stk[2]);
				
				var sStk=eLine.getAttribute("Stack");
				this._parse( eLine, rule, ruleIndex );
				
				if( bSingle ) return;
				
				var tm= (new Date()).getTime();
				eLine=this.updateGroup(rule,ruleIndex,eLine,sStk);
				
				//add to update list, check invalid item
				if(eLine)
				{
					tm= (new Date()).getTime()-tm;
					if( tm<1  ) tm=1;
					else if( tm>20 ) tm=20;
					
					if( !this.aUpdateList )
					{
						//void list, undefined
						this.aUpdateList=[];
						this.aUpdateList[0]=eLine;
						this.nUpdateLine=0;
					}
					else
					{
						var a=this.aUpdateList;
						var i=a.length-1;
						if( i<0 ) { a[0]=eLine; this.nUpdateLine=0; }	//void list
						else
						{
							var y0=eLine.offsetTop;
							var yi;
							for(;i>=0;i--)
							{
								if(a[i].parentNode.id!=this.eidText ) { a.splice(i,1); continue; }
								
								yi=a[i].offsetTop;
								//alert(yi+":"+y0)
								if( yi==y0 ) break;
								if( yi<y0 ) continue;
								//insert
								//alert("h "+i+":"+(a.length-1));
								if( i==(a.length-1) ) { a.push(eLine); this.nUpdateLine=0;}
								else a.splice( i+1,0,eLine);
								break;
							}
							
							if( i<0 )
							{
								//insert
								if( a.length==0 ) { a[0]=eLine; this.nUpdateLine=0; }
								else a.splice( 0,0,eLine);
							}
						}
					}
					
					//this.SetUpdateTimeout(tm);
					window.setTimeout( "$ec.bind['"+this.eid+"'].TimeoutFormat()", tm );
					
					//report
					var i= this.nUpdateLine;
					var imax=document.getElementById(this.eidText).childNodes.length;
					//window.status="formatting ~"+i+"/"+imax+ " (~"+Math.round(i*100/imax)+"% ) "+tm;
					this.Status("~"+i+"/"+imax+ " (~"+Math.round(i*100/imax)+"%) "+tm);
				}
			},
			
			TimeoutFormat: function()
			{
				
				var a= this.aUpdateList;
				if(!a) return;
				var i= a.length-1;
				if(i<0) return;
				
				var eLine;
				while( (eLine=a[i]).parentNode.id!=this.eidText )
				{
					a.pop();
					i--;
					if(i<0 )
					{
						//window.status="";
						this.Status("");
						return;
					}
				}
				
				if( !this.aStkCache2 ) this.aStkCache2=["",0,0];
				var stk=this.aStkCache2;
				this.getHeadStack( stk, eLine );
				var rule=stk[1];
				var ruleIndex=stk[2];
				var sStk=eLine.getAttribute("Stack");
				this._parse( eLine, rule, ruleIndex );
				//alert( eLine.innerHTML );
				
				var tm= (new Date()).getTime();
				eLine= this.updateGroup(rule,ruleIndex,eLine,sStk);
				
				if( eLine )
				{
					tm= (new Date()).getTime()-tm;
					if( tm<1  ) tm=1;
					else if( tm>20 ) tm=20;
					
					stk[0]=eLine.previousSibling.getAttribute("Stack");
					a[i]=eLine;
					
					//this.SetUpdateTimeout(tm);
					window.setTimeout( "$ec.bind['"+this.eid+"'].TimeoutFormat()", tm );
					
					//report
					this.nUpdateLine+=21;
					var i= this.nUpdateLine;
					var imax=document.getElementById(this.eidText).childNodes.length;
					//window.status="formatting ~"+i+"/"+imax+ " (~"+Math.round(i*100/imax)+"%) "+tm;
					this.Status( "~"+i+"/"+imax+ " (~"+Math.round(i*100/imax)+"%) "+ tm );
					
				}
				else
				{
					stk[0]="";	//clear cache
					a.pop();
					if(a.length>0 )
					{
						//this.SetUpdateTimeout(15);
						window.setTimeout( "$ec.bind['"+this.eid+"'].TimeoutFormat()", 10 )
					}
					else
					{
						//window.status="";
						this.Status("");
					}
				}
			},
			
			RefreshFormat: function()
			{
				var ei= $ec(this.eidText);
				var i=0,imax=ei.childNodes.length;
				ei=ei.firstChild;
				while(ei)
				{
					ei.setAttribute("Stack","");
					ei=ei.nextSibling;
				}
				this.FormatLine( 0 );
			},
			
			checkLast: function( eLine, bErr )
			{
				//?? ie6 bug: <ol>/last<li>: borderBottom abnormal
				eLine.style.borderBottom=(bErr || this.sEndStack.indexOf("|"+eLine.getAttribute("Stack")+"|")<0)?"2px red dashed":"none";
			},
			
			_format: function(s,sColor)
			{
				if( this.nBoldMode==1 )
				{
					if(sColor.slice(-1)=="B") sColor=sColor.slice(0,-1);
				}
				
				if(sColor)
				{
					return this.sBlockSpan1+sColor+this.sBlockSpan2+
							s.replace( /\&/g , "&amp;")
							.replace( /</g , "&lt;").replace( />/g , "&gt;")
							.replace( /\s/g ,"&nbsp;" )
							+this.sBlockSpan3;
				}
				else
				{
					return	s.replace( /\&/g , "&amp;")
							.replace( /</g , "&lt;").replace( />/g , "&gt;")
							.replace( /\s/g ,"&nbsp;" )
							;
				}
			},
			
			_parse: function( eLine, aRule, aRuleIndex )
			{
				var sa=[];
				var rule=aRule[aRule.length-1];
				var sParam=aRuleIndex[aRuleIndex.length-1];
				sParam= ( sParam.charAt(0)=="s" )?sParam.substring(1,sParam.length):"";
				
				var str=this.bTextContent?eLine.textContent.replace( /\r/g, "" ).replace( /\s/g, " " ):eLine.innerText;
				var i=0;
				var imax=str.length;
				
				//if(imax>2000) this.TRACE("Parse:"+imax);
				
				var er,j,jmax,ri,k;
				
				var bErr=0;
				//var imax2k= (imax<2000)?imax:2000;
				var imax2k= (imax<5000)?imax:5000;
				
				while(i<imax2k)
				{
					if( sParam && sParam==str.substring(i,i+sParam.length) )
					{
						j=rule.length-1;
						ri= rule[j];
						er=[sParam];
						
						if( ri[0]!="void" )
						{
							if(ri[0]=="err")
							{
								//$ec.TRACE("err.1");
								bErr=1;
							}
							
							sa.push( this._format(sParam,ri[0]) );
							i+=sParam.length;
						}
						else if( ri[2]<0 && aRule.length<2 )	// avoid invalid pop back and loop
						{
							sa.push( this._format(sParam,"err") );
							bErr=1;
							//$ec.TRACE("err.2");
							i+=sParam.length;
						}
					}
					else
					{
						rule[0].lastIndex=i;
						er= rule[0].exec( str );
					
						//matched
						jmax=er.length;
						for(j=1;j<jmax;j++) if( er[j] ) break;		//get matched sequence
						if(j==jmax)j=jmax-1;
						
						ri= rule[j];
						
						if( er.index!=i )
						{
							if(ri[0]=="err"||ri[1]=="err")
							{
								//$ec.TRACE("err.3 "+j);
								//$ec.TRACE(ri);
								bErr=1;
							}
							
							//text before match
							sa.push( this._format(str.substring(i,er.index),ri[1]) );
						}
						else if(ri[0]=="err" || ri[1]=="errx")
						{
							//$ec.TRACE("err.4");
							bErr=1;
						}
						
						//matched text
						i=er.index;
						if(er[0])
						{
							if( ri[0]!="void" )
							{
								sa.push( this._format(er[0],ri[0]) );
								i+=er[0].length;		//don't use er.lastIndex, un-readable in ff 2.0
							}
							else if( ri[2]<0 && aRule.length<2 )	// avoid invalid pop back and loop
							{
								sa.push( this._format(er[0],"err") );
								i+=er[0].length;
								//$ec.TRACE("err.5");
								bErr=1;
							}
						}
					}
					
					k=ri[2];
					if(k==1)
					{
						//push
						aRule[aRule.length]=ri[4];	//don't use push(), for ri[4] is array
						
						aRuleIndex.push(j.toString());
						if(ri[3])
						{
							sParam= ri[3].replace( "$1", er[0] ).replace( "$P", sParam );
							aRuleIndex.push( "s"+sParam );
						}
						else sParam="";

						rule=ri[4];
					}
					else if(k<0 )
					{
						do
						{
							//pop
							if( aRule.length>1 )
							{
								aRule.pop();
								if( aRuleIndex.pop().charAt(0)=="s") aRuleIndex.pop();
							}
							else break;
							
							k+=1;
						} while(k<0);
						
						if(i<imax)
						{
							rule=aRule[aRule.length-1];
							sParam=aRuleIndex[aRuleIndex.length-1];
							sParam= ( sParam.charAt(0)=="s" )?sParam.substring(1,sParam.length):"";
						}
					}
				}
				if( i<imax )
				{
					sa.push( this._format(str.substring(i,str.length)) );
				}
				
				eLine.setAttribute("Stack",aRuleIndex.join(this.sStackSplit));
				
				//return flag
				if(this.bLineBreak)
				{
					sa[sa.length]= this._format("","return");
					//if( sa.length==1 && !window.ActiveXObject) sa[sa.length]="<br>";
					//if( sa.length==1 && window.opera ) eLine.style.height=this.nLineHeight+"px";
				}
				
				var sAll= (sa.length>0)?sa.join(""):"";
				
				//if(eLine==this.objTop.lastChild)
				var ep= eLine.parentNode;
				if(
					eLine===ep.lastChild ||
					( eLine===ep.lastChild.previousSibling && ep.lastChild.id==this.eidTmp)
					)
				{
					//last line
					eLine.style.borderBottom="";
					//this.TRACE(bErr);
					this.checkLast(eLine, bErr);	//ie6 bug
				}
				else
				{
					//this.TRACE( bErr+":"+sAll );
					eLine.style.borderBottom=bErr?"1px dashed red":"";
				}
				
				//$ec.TRACE("set sAll:["+sAll+"]");
				if( eLine.innerHTML!= sAll )
				{
					//alert( eLine.innerHTML +"\n==\n"+sAll );
					/*
					if( sAll=="" )	//for firefox
					{
						//if(!window.ActiveXObject)
						if(this.sAgent=="firefox")
						{
							//if( eLine.innerHTML=="<br>" ) return false;
							//else sAll="<br>";
							//$ec.TRACE("insert <br>");
							sAll=="<br>";
						}
					}
					*/
					
					eLine.innerHTML= sAll;
					return true;
				}
				/*
				else if( sAll=="" )
				{
					//if(!window.ActiveXObject)
					if(this.sAgent=="firefox")
					{
						//for firefox
						eLine.innerHTML= "<br>";
						//$ec.TRACE("insert <br>");
						return true;
					}
					//return false;
				}
				else return false;
				*/
				return true;
			},
			
			//////////////////
			//text content function group
			//////////////////
			
			initRule: function( bInit )
			{
				//init rule state
				this.aStkCache1=null;
				this.aStkCache2=null;
				
				var eiFirst=$ec(this.eidText).firstChild;
				var ei=eiFirst;
				if(ei && !bInit)
				{
					ei.setAttribute("Stack","");
					while(ei=ei.nextSibling) ei.setAttribute("Stack","");
				}
				
				//$ec.CALLSTACK();
				this.FormatLine(0);
				
				if(this.pos)	//for SyntaxEditor
				{
					this.SetBlock(0,0,-1);
					this.UpdateCaret(this.bFocus);
				}
			},
			
			//GetText: function([sLineSplit])
			GetText: function(sLineSplit)
			{
				return this.GetTextEx([]).join(sLineSplit?sLineSplit:"\n");
			},
			
			//GetTextEx: function(a)
			//a: array to receive line text
			//return the a
			GetTextEx: function(a)
			{
				var ep= $ec(this.eidText);
				var ei= ep.firstChild;
				
				if(this.bTextContent)
				{
					while(ei) { a[a.length]=ei.textContent.replace( /\r/g, "" ).replace( /\s/g, " " ); ei=ei.nextSibling; }
				}
				else
				{
					while(ei) { a[a.length]=ei.innerText; ei=ei.nextSibling; }
				}
				if(ep.lastChild.id==this.eidTmp) a.pop();
				return a;
			},
			
			//expand tab to 4 spaces
			expandTab: function( s, n )
			{
				var a=s.split("\t");
				var j,jmax;
				jmax=a.length-1;
				for(j=0;j<jmax;j++)
				{
					n+=a[j].length;
					switch(n%4)
					{
						case 0: a[j]+="    "; n+=4; break;
						case 1: a[j]+="   "; n+=3; break;
						case 2: a[j]+="  "; n+=2; break;
						case 3: a[j]+=" "; n+=1; break;
					}
				}
				return a.join("");
			},
			
			//SetText: function( s [, bModified] )
			SetText: function( s, bModified )
			{
				if( this.pos ) this.resetBlock();	//for SyntaxEditor
				
				if( bModified )		//for SyntaxEditor
				{
					this.bModified= true;
					$ec.$notify( this, "before-modify", null );
				}
				
				//normalize
				var expandTab=this.expandTab;

				var a=s.replace( /\r/g,"" ).split("\n");
				var i,j,jmax,imax=a.length;
				for(i=0;i<imax;)
				{
					jmax=i+200; if(jmax>imax)jmax=imax;
					for(j=i;j<jmax;j++) a[j]=expandTab(a[j],0);
					i=jmax;
					window.status="normalizing "+i+"/"+imax+ " ("+Math.round(i*100/imax)+"%)";
					//this.Status("normalizing "+i+"/"+imax+ " ("+Math.round(i*100/imax)+"%)");
				}
				window.status="";
				//this.Status("");
				
				var ep= $ec(this.eidText);
				ep.innerHTML=( "<li>"+
					a.join("\n").replace(/ /g,"\xA0")
					.replace( /\&/g , "&amp;").replace( /</g , "&lt;").replace( />/g , "&gt;").
					split("\n").join("</li><li>")+
					"</li>"). replace( /<li><\/li>/g, "<li style='min-height:1em;'></li>" );
					;
				
				if(this.aOp) this.aOp.splice(0,this.aOp.length);
				this.nOpIdx=0;
				if(this.aUpdateList) this.aUpdateList.splice(0,this.aUpdateList.length);
				this.nUpdateLine=0;
				
				this.updateLineNumber(1);
				this.initRule();
				
				if( this.sAgent=="firefox" && s.length>20000 ) this._designMode_ON_mz();
			},
			
			//////////////////
			//other
			//////////////////
			
			/*
			bug 1: firefox2.0/3.0, when text is very long( ~4000 lines), firefox scroll bar will become very slow when
			it start a scroll work, such as when start dragging scrollbar(after a few seconds it becomes normal), or when
			click scrollbar to page up/down.----xxx----cause by TabIndex
			
			bug 2: !!! Firefox2.0 serious bug, after document.designMode="off", all <input> cann't be focus, in same
			browser window. Already modified in Firefox3.0.
			*/
			
			//not work in firefox3.0, but keep
			_designMode_ON_mz: function()
			{
				//speed up for firefox 2.0/3.0
				//document.designMode="on";
				//document.designMode="off";
				//$ec.TRACE("designMode speed up");
			}
			
//////////////////////////////
// END EC-CLASS BODY	//NOTE - DO NOT CHANGE THESE 3 LINES, the ide wizards may use them.
//////////////////////////////
            }
		);
		
		//add default text rule
		this.prototype.AddRule(this.prototype.oRuleText);
		this.prototype.SetRule(this.prototype.oRuleText);
		
		//#EC_RES
		//#FILE: {id:"SyntaxEditorBack_gif",path:"SyntaxEditorBack.gif",dyna:1}
		//#END_EC_RES
		
	}
);
